package czopyk.model.factories;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import czopyk.model.cost.DistributionId;
import czopyk.model.cost.DistributionManager;
import czopyk.model.request.CompositeRequest;
import czopyk.model.request.LoadClass;
import czopyk.model.request.Request;
import czopyk.model.request.RequestClass;
import czopyk.model.request.RequestType;

public class RequestFactory {

	private static final Logger l = Logger.getLogger("RequestFactory");

	private DistributionManager distributionManager;

	private final Map<RequestType, Map<RequestClass, RequestTemplate>> registered = new HashMap<RequestType, Map<RequestClass, RequestTemplate>>();
	private final Set<LoadClass> load = new HashSet<LoadClass>();

	public Set<RequestClass> getClassesForType(RequestType type) {
		return registered.get(type).keySet();
	}

	public LoadClass registerRequestClass(String type, String clazz,
			final Map<LoadClass, DistributionId> components) {

		final RequestType requestType = type != null ? new RequestType(type) : null;
		final RequestClass requestClass = clazz != null ? new RequestClass(clazz) : null;
		final boolean composite = components != null && !components.isEmpty();

		final LoadClass loadClass = new LoadClass() {

			@SuppressWarnings("unchecked")
			final Map<LoadClass, DistributionId> cc = Collections
					.unmodifiableMap(components != null ? components
							: Collections.EMPTY_MAP);

			public RequestType getType() {
				return requestType;
			}

			public RequestClass getClazz() {
				return requestClass;
			}

			public boolean isComposite() {
				return composite;
			}

			public Map<LoadClass, DistributionId> getComponents() {
				return cc;
			}
		};

		if (load.contains(loadClass)) {
			throw new IllegalStateException("Class " + loadClass
					+ " already registered.");
		}

		load.add(loadClass);

		Map<RequestClass, RequestTemplate> map = registered.get(requestType);
		if (map == null) {
			map = new HashMap<RequestClass, RequestTemplate>();
			registered.put(requestType, map);
		}

		map.put(requestClass, new RequestTemplate() {

			public Request getRequest() {
				if (composite) {
					CompositeRequest cr = new CompositeRequest(loadClass);

					for (LoadClass lc : components.keySet()) {
						int i = distributionManager.getDistributionIntValue(components
								.get(lc));
						l.debug("adding " + i + " times " + lc + " to " + loadClass);
						while (i-- > 0) {
							cr.getComponents().add(
									registered.get(lc.getType()).get(lc.getClazz()).getRequest());
						}
					}
					if (cr.getComponents().isEmpty()) {
						l.error("No children component requests for composite request '"
								+ loadClass + "'. Setting to done.");
						cr.done("no children");
					}
					return cr;
				} else {
					return new Request(loadClass);
				}
			}
		});

		return loadClass;
	}

	interface RequestTemplate {
		public Request getRequest();
	};

	public LoadClass getLoadClass(String type, String clazz) {
		type = type == null ? "" : type;
		clazz = clazz == null ? "" : clazz;

		for (LoadClass lc : load) {
			if (lc.getClazz().getValue().equals(clazz)
					&& lc.getType().getValue().equals(type)) {
				return lc;
			}
		}
		return null;
	}

	public Request getRequest(LoadClass lc) {
		Request r = registered.get(lc.getType()).get(lc.getClazz()).getRequest();
		l.debug("Producing request: " + r);
		setParent(r, r);
		return r;
	}

	/*
	 * public RequestTemplate getRequestTemplate(LoadClass lc) { return
	 * registered.get(lc.getClazz()).get(lc.getType()); }
	 */
	private void setParent(Request r, Request parent) {
		r.setParent(parent);
		if (r instanceof CompositeRequest) {
			for (Request s : ((CompositeRequest) r).getComponents()) {
				setParent(s, parent);
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("RequestFactory; registered: "
				+ Arrays.toString(registered.keySet().toArray()) + "\n");
		for (RequestType rt : registered.keySet()) {
			for (RequestClass rc : registered.get(rt).keySet()) {
				sb.append(l
						+ " -> "
						+ registered.get(rt).get(rc).getRequest().getClass()
								.getSimpleName() + "\n");
			}
		}
		return sb.toString();
	}

	public void setDistributionManager(DistributionManager distributionManager) {
		this.distributionManager = distributionManager;
	}

}
